﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using OpenLS.Spreadsheet.Expressions;
using OpenLS.Spreadsheet.Formats.Biff.Tokens;

namespace OpenLS.Spreadsheet.Formats.Biff.Records
{
    internal class CFFormulaContext
    {
        private readonly Cell _cell;

        public CFFormulaContext(Cell cell)
        {
            _cell = cell;
        }

        public Cell Cell
        {
            get { return _cell; }
        }
    }

    internal class CFRecord : WorksheetLevelRecord
    {
        private CFComparisonFunction cp;
        private ConditionType ct;
        private DXFN rgbdxf;

        private CFParsedFormulaNoCCE rgce2;

        public CFRecord(ConditionalFormattingRule rule, BiffWriterWorkbookContext context, Cell topLeftCell)
        {
            Contract.Requires(!(rule is ColorScaleFormattingRule));
            Contract.Requires(!(rule is DataBarFormattingRule));
            Contract.Requires(!(rule is IconSetFormattingRule));
            Contract.Ensures(rgce1 != null);
            if (rule is ExpressionFormattingRule)
            {
                var expressionFormattingRule = (ExpressionFormattingRule) rule;
                ct = ConditionType.Rgce1Only;
                cp = CFComparisonFunction.None;
                rgbdxf.SetFromDifferencialStyleRecord(expressionFormattingRule.DifferentialStyleRecord, context);
                var expressionformula = (ExpressionFormula) expressionFormattingRule.FormulaObject1;
                rgce1 = ParsedFormulaBase.GetFormulaValue<CFParsedFormulaNoCCE>(context, expressionformula.Expression,
                                                                                topLeftCell);
                return;
            }
            if (rule is CellIsFormattingRule)
            {
                var cellIsFormattingRule = (CellIsFormattingRule) rule;
                var expressionFormula = (ExpressionFormula) cellIsFormattingRule.FormulaObject1;
                rgbdxf.SetFromDifferencialStyleRecord(cellIsFormattingRule.DifferentialStyleRecord, context);
                cp = GetComparisonFunction(rule);
                switch (cellIsFormattingRule.Operator)
                {
                    case ConditionalFormattingOperator.LessThan:
                        ct = ConditionType.Rgce1Only;
                        rgce1 = ParsedFormulaBase.GetFormulaValue<CFParsedFormulaNoCCE>(context,
                                                                                        expressionFormula.Expression,
                                                                                        topLeftCell);
                        return;
                    case ConditionalFormattingOperator.Equal:
                        ct = ConditionType.Both; //\\why?
                        rgce1 = ParsedFormulaBase.GetFormulaValue<CFParsedFormulaNoCCE>(context,
                                                                                        expressionFormula.Expression,
                                                                                        topLeftCell);
                        return;
                    case ConditionalFormattingOperator.BeginsWith:
                    case ConditionalFormattingOperator.Between:
                    case ConditionalFormattingOperator.ContainsText:
                    case ConditionalFormattingOperator.EndsWith:
                    case ConditionalFormattingOperator.GreaterThan:
                    case ConditionalFormattingOperator.GreaterThanOrEqual:
                    case ConditionalFormattingOperator.LessThanOrEqual:
                    case ConditionalFormattingOperator.NotBetween:
                    case ConditionalFormattingOperator.NotContains:
                    case ConditionalFormattingOperator.NotEqual:
                        throw new NotImplementedException();
                    default:
                        throw new NotSupportedException();
                }
            }
            if (rule is AboveAverageFormattingRule)
            {
                var expressionFormattingRule = (AboveAverageFormattingRule) rule;
                ct = ConditionType.Rgce1Only;
                cp = CFComparisonFunction.None;
                rgbdxf.SetFromDifferencialStyleRecord(expressionFormattingRule.DifferentialStyleRecord, context);
                Debug.WriteLine(".");
                Contract.Assert(expressionFormattingRule != null);
                Contract.Assert(expressionFormattingRule.FormulaObject1 != null);
                var expressionformula = (ExpressionFormula) expressionFormattingRule.FormulaObject1;
                Contract.Assert(expressionformula != null);
                rgce1 = ParsedFormulaBase.GetFormulaValue<CFParsedFormulaNoCCE>(context, expressionformula.Expression,
                                                                                topLeftCell);
                return;
            }
            if (rule is BeginsWithFormattingRule)
            {
                var beginsWithFormattingRule = (BeginsWithFormattingRule) rule;
                ct = ConditionType.Rgce1Only;
                cp = CFComparisonFunction.None;
                rgbdxf.SetFromDifferencialStyleRecord(beginsWithFormattingRule.DifferentialStyleRecord, context);
                var expressionformula = (ExpressionFormula) beginsWithFormattingRule.FormulaObject1;
                rgce1 = ParsedFormulaBase.GetFormulaValue<CFParsedFormulaNoCCE>(context, expressionformula.Expression,
                                                                                topLeftCell);
                return;
            }
            if (rule is EndsWithFormattingRule)
            {
                var endsWithFormattingRule = (EndsWithFormattingRule) rule;
                ct = ConditionType.Rgce1Only;
                cp = CFComparisonFunction.None;
                rgbdxf.SetFromDifferencialStyleRecord(endsWithFormattingRule.DifferentialStyleRecord, context);
                var expressionformula = (ExpressionFormula) endsWithFormattingRule.FormulaObject1;
                rgce1 = ParsedFormulaBase.GetFormulaValue<CFParsedFormulaNoCCE>(context, expressionformula.Expression,
                                                                                topLeftCell);
                return;
            }
            if (rule is ContainsTextFormattingRule)
            {
                var containsTextFormattingRule = (ContainsTextFormattingRule) rule;
                ct = ConditionType.Rgce1Only;
                cp = CFComparisonFunction.None;
                rgbdxf.SetFromDifferencialStyleRecord(containsTextFormattingRule.DifferentialStyleRecord, context);
                var expressionformula = (ExpressionFormula) containsTextFormattingRule.FormulaObject1;
                if (expressionformula != null) //\\
                    rgce1 = ParsedFormulaBase.GetFormulaValue<CFParsedFormulaNoCCE>(context,
                                                                                    expressionformula.Expression,
                                                                                    topLeftCell);
                return;
            }
            if (rule is NotContainsTextFormattingRule)
            {
                var notContainsTextFormattingRule = (NotContainsTextFormattingRule) rule;
                ct = ConditionType.Rgce1Only;
                cp = CFComparisonFunction.None;
                rgbdxf.SetFromDifferencialStyleRecord(notContainsTextFormattingRule.DifferentialStyleRecord, context);
                var expressionformula = (ExpressionFormula) notContainsTextFormattingRule.FormulaObject1;
                rgce1 = ParsedFormulaBase.GetFormulaValue<CFParsedFormulaNoCCE>(context, expressionformula.Expression,
                                                                                topLeftCell);
                return;
            }
            if (rule is ContainsBlankFormattingRule)
            {
                var containsBlackFormattingRule = (ContainsBlankFormattingRule) rule;
                ct = ConditionType.Rgce1Only;
                cp = CFComparisonFunction.None;
                rgbdxf.SetFromDifferencialStyleRecord(containsBlackFormattingRule.DifferentialStyleRecord, context);
                var expressionFormula = (ExpressionFormula) containsBlackFormattingRule.FormulaObject1;
                rgce1 = ParsedFormulaBase.GetFormulaValue<CFParsedFormulaNoCCE>(context, expressionFormula.Expression,
                                                                                topLeftCell);
                return;
            }
            if (rule is ContainsErrorsFormattingRule)
            {
                var containsErrorsFormattingRule = (ContainsErrorsFormattingRule) rule;
                ct = ConditionType.Rgce1Only;
                cp = CFComparisonFunction.None;
                rgbdxf.SetFromDifferencialStyleRecord(containsErrorsFormattingRule.DifferentialStyleRecord, context);
                var expressionFormula = (ExpressionFormula) containsErrorsFormattingRule.FormulaObject1;
                rgce1 = ParsedFormulaBase.GetFormulaValue<CFParsedFormulaNoCCE>(context, expressionFormula.Expression,
                                                                                topLeftCell);
                return;
            }
            if (rule is DuplicateValuesFormattingRule)
            {
                var r = (DuplicateValuesFormattingRule) rule;
                ct = ConditionType.Rgce1Only;
                cp = CFComparisonFunction.None;
                rgbdxf.SetFromDifferencialStyleRecord(r.DifferentialStyleRecord, context);
                var expressionFormula = (ExpressionFormula) r.FormulaObject1;
                rgce1 = ParsedFormulaBase.GetFormulaValue<CFParsedFormulaNoCCE>(context, expressionFormula.Expression,
                                                                                topLeftCell);
                return;
            }
            if (rule is UniqueValuesFormattingRule)
            {
                var r = (UniqueValuesFormattingRule) rule;
                ct = ConditionType.Rgce1Only;
                cp = CFComparisonFunction.None;
                rgbdxf.SetFromDifferencialStyleRecord(r.DifferentialStyleRecord, context);
                var expressionFormula = (ExpressionFormula) r.FormulaObject1;
                rgce1 = ParsedFormulaBase.GetFormulaValue<CFParsedFormulaNoCCE>(context, expressionFormula.Expression,
                                                                                topLeftCell);
                return;
            }
            if (rule is NotContainsBlanksFormattingRule || rule is NotContainsErrorsFormattingRule)
            {
                var r = (StandardConditionalFormattingRule) rule;
                ct = ConditionType.Rgce1Only;
                cp = CFComparisonFunction.None;
                rgbdxf.SetFromDifferencialStyleRecord(r.DifferentialStyleRecord, context);
                var expressionFormula = (ExpressionFormula) r.FormulaObject1;
                rgce1 = ParsedFormulaBase.GetFormulaValue<CFParsedFormulaNoCCE>(context, expressionFormula.Expression,
                                                                                topLeftCell);
                return;
            }
            if (rule is TimePeriodFormattingRule)
            {
                var r = (StandardConditionalFormattingRule) rule;
                ct = ConditionType.Rgce1Only;
                cp = CFComparisonFunction.None;
                rgbdxf.SetFromDifferencialStyleRecord(r.DifferentialStyleRecord, context);
                var expressionFormula = (ExpressionFormula) r.FormulaObject1;
                rgce1 = ParsedFormulaBase.GetFormulaValue<CFParsedFormulaNoCCE>(context, expressionFormula.Expression,
                                                                                topLeftCell);
                return;
            }
            throw new NotSupportedException(rule.ToString());
        }

        public CFRecord()
        {
        }

        private CFParsedFormulaNoCCE rgce1 { get; set; }

        internal override BiffRecordType RecordType
        {
            get { return BiffRecordType.CF; }
        }

        internal static CFComparisonFunction GetComparisonFunction(ConditionalFormattingRule rule)
        {
            if (rule is ExpressionFormattingRule)
            {
                return CFComparisonFunction.None;
            }
            if (rule is CellIsFormattingRule)
            {
                var cellIsFormattingRule = (CellIsFormattingRule) rule;
                switch (cellIsFormattingRule.Operator)
                {
                    case ConditionalFormattingOperator.LessThan:
                        return CFComparisonFunction.LessThanV1;
                    case ConditionalFormattingOperator.Equal:
                        return CFComparisonFunction.EqualV1;
                    case ConditionalFormattingOperator.BeginsWith:
                    case ConditionalFormattingOperator.Between:
                    case ConditionalFormattingOperator.ContainsText:
                    case ConditionalFormattingOperator.EndsWith:
                    case ConditionalFormattingOperator.GreaterThan:
                    case ConditionalFormattingOperator.GreaterThanOrEqual:
                    case ConditionalFormattingOperator.LessThanOrEqual:
                    case ConditionalFormattingOperator.NotBetween:
                    case ConditionalFormattingOperator.NotContains:
                    case ConditionalFormattingOperator.NotEqual:
                        throw new NotImplementedException();
                    default:
                        throw new NotSupportedException();
                }
            }
            if (rule is AboveAverageFormattingRule)
            {
                return CFComparisonFunction.None;
            }
            if (rule is BeginsWithFormattingRule)
            {
                return CFComparisonFunction.None;
            }
            if (rule is EndsWithFormattingRule)
            {
                return CFComparisonFunction.None;
            }
            if (rule is ContainsTextFormattingRule)
            {
                return CFComparisonFunction.None;
            }
            if (rule is NotContainsTextFormattingRule)
            {
                return CFComparisonFunction.None;
            }
            if (rule is ContainsBlankFormattingRule)
            {
                return CFComparisonFunction.None;
            }
            if (rule is ContainsErrorsFormattingRule)
            {
                return CFComparisonFunction.None;
            }
            if (rule is DuplicateValuesFormattingRule)
            {
                return CFComparisonFunction.None;
            }
            if (rule is UniqueValuesFormattingRule)
            {
                return CFComparisonFunction.None;
            }
            if (rule is NotContainsBlanksFormattingRule || rule is NotContainsErrorsFormattingRule)
            {
                return CFComparisonFunction.None;
            }
            if (rule is TimePeriodFormattingRule)
            {
                return CFComparisonFunction.None;
            }
            throw new NotImplementedException(rule.ToString());
        }

        internal override void Apply(BiffReaderWorksheetContext context)
        {
            ((CondFmtRecord) context.CurrentCondFmtRecord).CFRecords.Add(this);
            /*   Contract.Assert(context.CurrentConditionalFormatting != null);
            var rule = ToNakedRule(context, context.CurrentConditionalFormatting.Range.TopLeftCell);
            rule.DifferentialStyleRecord =
                this.rgbdxf.ToDifferencialStyleRecord(context.WorkbookContext);
            context.CurrentConditionalFormatting.Rules.Add(rule);*/
        }

        public static StandardConditionalFormattingRule ToRule(BiffReaderWorksheetContext context, Cell target
                                                               , CFRecord cfRecord, CFExRecord cfExRecord)
        {
            StandardConditionalFormattingRule result = ToNakedRule(context, cfRecord, cfExRecord, target);
            result.DifferentialStyleRecord = cfRecord.rgbdxf.ToDifferencialStyleRecord(context.WorkbookContext,
                                                                                       cfExRecord);
            if (cfExRecord != null)
                result.Priority = cfExRecord.rgbContent.ipriority;

            return result;
        }

        private static StandardConditionalFormattingRule ToNakedRule(BiffReaderWorksheetContext context,
                                                                     CFRecord cfRecord, CFExRecord cfExRecord,
                                                                     Cell target)
        {
            switch (cfRecord.cp)
            {
                case CFComparisonFunction.None:
                    {
                        if (cfExRecord != null)
                        {
                            CFExTemplateParams v = cfExRecord.TemplateParameters;
                            if (v is CFExAveragesTemplateParams)
                            {
                                var cfExAveragesTemplateParams = (CFExAveragesTemplateParams) v;
                                var AboveAverageFormattingRule = new AboveAverageFormattingRule
                                                                     {
                                                                         FormulaObject1 =
                                                                             new CellExpressionFormula(
                                                                             cfRecord.rgce1.GetExpression(context,
                                                                                                          target),
                                                                             target)
                                                                     };
                                return AboveAverageFormattingRule;
                            }
                            if (v is CFExTextTemplateParams)
                            {
                                var cfExTextTemplateParams = (CFExTextTemplateParams) v;
                                var formulaObject1 = new CellExpressionFormula(
                                    cfRecord.rgce1.GetExpression(context, target),
                                    target)
                                    ;
                                string text = ExtractString(formulaObject1, cfExTextTemplateParams.TextRuleType);
                                switch (cfExTextTemplateParams.TextRuleType)
                                {
                                    case TextTemplateParamsType.TextBeginsWith:

                                        return new BeginsWithFormattingRule
                                                   {
                                                       FormulaObject1 = formulaObject1,
                                                       Text = text
                                                   };
                                    case TextTemplateParamsType.TextContains:
                                        return new ContainsTextFormattingRule
                                                   {
                                                       FormulaObject1 = formulaObject1,
                                                       Text = text
                                                   };
                                    case TextTemplateParamsType.TextDoesNotContain:
                                        return new NotContainsTextFormattingRule
                                                   {
                                                       FormulaObject1 = formulaObject1,
                                                       Text = text
                                                   };
                                    case TextTemplateParamsType.TextEndsWith:
                                        return new EndsWithFormattingRule
                                                   {
                                                       FormulaObject1 = formulaObject1,
                                                       Text = text
                                                   };
                                    default:
                                        throw new NotSupportedException();
                                }
                            }
                            else if (v is CFExDefaultTemplateParams)
                            {
                            }
                            else
                            {
                                // Contract.Assert(false);
                            }
                        }
                        Expression expression = cfRecord.rgce1.GetExpression(context, new CFFormulaContext(target));
#if DEBUG
                        Debug.WriteLine("Getting cf");
                      //  Debug.WriteLine(string.Join("|", cfRecord.rgce1.GetTokens()));
                        Debug.WriteLine(expression.GetText(new TextContext(new CellEvaluationContext(target))));

#endif
                        return new ExpressionFormattingRule
                                         {
                                             FormulaObject1 =
                                                 new CellExpressionFormula(
                                                 expression,
                                                 target)
                                         };
                    }
                case CFComparisonFunction.LessThanV1:
                    {
                        return new CellIsFormattingRule
                                         {
                                             Operator = ConditionalFormattingOperator.LessThan,
                                             FormulaObject1 =
                                                 new CellExpressionFormula(
                                                 cfRecord.rgce1.GetExpression(context, target),
                                                 target)
                                         };
                    }
                case CFComparisonFunction.EqualV1:
                    {
                        return new CellIsFormattingRule
                                         {
                                             Operator = ConditionalFormattingOperator.Equal,
                                             FormulaObject1 =
                                                 new CellExpressionFormula(
                                                 cfRecord.rgce1.GetExpression(context, target),
                                                 target)
                                         };
                    }
                case CFComparisonFunction.NotEqualToV1:
                    {
                        return new CellIsFormattingRule
                                         {
                                             Operator = ConditionalFormattingOperator.NotEqual,
                                             FormulaObject1 =
                                                 new CellExpressionFormula(
                                                 cfRecord.rgce1.GetExpression(context, target),
                                                 target)
                                         };
                    }
                case CFComparisonFunction.GreaterThanOrEqualToV1:
                    {
                        return new CellIsFormattingRule
                                         {
                                             Operator = ConditionalFormattingOperator.GreaterThanOrEqual,
                                             FormulaObject1 =
                                                 new CellExpressionFormula(
                                                 cfRecord.rgce1.GetExpression(context, target),
                                                 target)
                                         };
                    }
                case CFComparisonFunction.GreaterThanV1:
                    {
                        return new CellIsFormattingRule
                                         {
                                             Operator = ConditionalFormattingOperator.GreaterThan,
                                             FormulaObject1 =
                                                 new CellExpressionFormula(
                                                 cfRecord.rgce1.GetExpression(context, target),
                                                 target)
                                         };
                    }
                case CFComparisonFunction.LessThanOrEqualToV1:
                    {
                        return new CellIsFormattingRule
                                   {
                                       Operator = ConditionalFormattingOperator.LessThanOrEqual,
                                       FormulaObject1 =
                                           new CellExpressionFormula(
                                           cfRecord.rgce1.GetExpression(context, target),
                                           target)
                                   };
                    }
                case CFComparisonFunction.Between:
                    return new CellIsFormattingRule
                               {
                                   Operator = ConditionalFormattingOperator.Between,
                                   FormulaObject1 =
                                       new CellExpressionFormula(
                                       cfRecord.rgce1.GetExpression(context, target),
                                       target),
                                   FormulaObject2 =
                                       new CellExpressionFormula(
                                       cfRecord.rgce2.GetExpression(context, target),
                                       target)
                               };
                case CFComparisonFunction.NotBetween:
                    return new CellIsFormattingRule
                               {
                                   Operator = ConditionalFormattingOperator.NotBetween,
                                   FormulaObject1 =
                                       new CellExpressionFormula(
                                       cfRecord.rgce1.GetExpression(context, target),
                                       target),
                                   FormulaObject2 =
                                       new CellExpressionFormula(
                                       cfRecord.rgce2.GetExpression(context, target),
                                       target)
                               };
                default:
                    throw new NotSupportedException();
            }
        }

        private static string ExtractString(CellExpressionFormula formulaObject1, TextTemplateParamsType type)
        {
            Expression expression = formulaObject1.Expression;
            if (expression is BooleanConstantExpression)
                return null;
#if DEBUG
            List<Expression> test = Enumerable.ToList(expression.GetSubExpressions());
            Debug.WriteLine(expression.ToString());
#endif
            StringConstantExpression constantExpression =
                Enumerable.First(
                    Enumerable.OfType<StringConstantExpression>(ExpressionHelper.GetAllExpressions(expression)));
/*            StringConstantExpression constantExpression = expression is StringConstantExpression
                                                              ? (StringConstantExpression) expression
                                                              : Enumerable.First(
                                                                  Enumerable.OfType<StringConstantExpression>(
                                                                     ExpressionHelper.GetAllExpressions(expression));
 * */
            return constantExpression.StringValue;
        }

        internal override void WriteContent(BiffWriterWorkbookContext context)
        {
            Contract.Assert(rgce1 != null);
            context.Write((byte) ct);
            context.Write((byte) cp);
            ushort ui = rgce1.TotalTokenByteLength(FormulaReadContextType.Default);
            context.Write(ui);
            ui = (rgce2 != null ? rgce2.TotalTokenByteLength(FormulaReadContextType.Default) : (ushort) 0);
            context.Write(ui);
            rgbdxf.Write(context.Writer);

            rgce1.WriteMe(new FormulaWriteContext(context.Writer, FormulaReadContextType.Default));
            if (rgce2 != null)
                rgce2.WriteMe(new FormulaWriteContext(context.Writer, FormulaReadContextType.Default));
        }

        protected override void ReadContent(BiffReaderContext context, int length)
        {
            ct = (ConditionType) context.ReadByte();
            cp = (CFComparisonFunction) context.ReadByte();
            ushort cce1 = context.ReadUInt16();
            ushort cce2 = context.ReadUInt16();
            rgbdxf.Read(context.Reader);

            rgce1 = new CFParsedFormulaNoCCE();
            rgce1.ReadMe(new FormulaReadContext(context.Reader, FormulaReadContextType.Default), cce1);
            rgce2 = new CFParsedFormulaNoCCE();
            rgce2.ReadMe(new FormulaReadContext(context.Reader, FormulaReadContextType.Default), cce2);
        }

        #region Nested type: ConditionType

        private enum ConditionType : byte
        {
            Both = 1,
            Rgce1Only = 2,
        }

        #endregion
    }

    internal enum CFComparisonFunction : byte
    {
        None = 0x0,
        Between = 0x01,
        NotBetween = 0x02,
        EqualV1 = 0x03,
        NotEqualToV1 = 0x04,
        GreaterThanV1 = 0x05,
        LessThanV1 = 0x06,
        GreaterThanOrEqualToV1 = 0x07,
        LessThanOrEqualToV1 = 0x08,
    }
}